package com.tengju.bff.interfaces.covercharge.util.alarmUtil;

import org.slf4j.Logger;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.Set;

/**
 * 异常工具类
 *
 * @author wzy
 */
public class ExceptionUtil {

    /**
     * 将错误栈报错到日志文件
     *
     * @param logger
     * @param e
     */
    public static void logger(final Logger logger, final Exception e) {
        logger.error(getStackTraceAsString(e));
    }

    /**
     * 将CheckedException转换为UncheckedException.
     */
    public static RuntimeException unchecked(final Throwable e) {
        if (e instanceof RuntimeException) {
            return (RuntimeException) e;
        } else {
            return new RuntimeException(e);
        }
    }

    /**
     * 将ErrorStack转化为String.
     */
    public static String getStackTraceAsString(final Throwable e) {
        try {
            final StringWriter stringWriter = new StringWriter();
            e.printStackTrace(new PrintWriter(stringWriter));
            String s = stringWriter.toString();
            return (s == null || s.length() == 0) ? e.getMessage() : s;
        } catch (Exception e1) {
            return "StackTraceAsString error:" + e.getMessage();
        }
    }

    /**
     * 判断异常是否由某些底层的异常引起.
     */
    public static boolean isCausedBy(final Exception ex, final Class<? extends Exception>... causeExceptionClasses) {
        Throwable cause = ex.getCause();
        while (cause != null) {
            for (final Class<? extends Exception> causeClass : causeExceptionClasses) {
                if (causeClass.isInstance(cause)) {
                    return true;
                }
            }
            cause = cause.getCause();
        }
        return false;
    }

    /**
     * 打印日志
     *
     * @param e
     * @return
     */
    public static String getStackTraceAsStringLimit(final Throwable e) {
        try {
            final StringWriter stringWriter = new StringWriter();
            WrappedPrintWriter wrappedPrintWriter = new WrappedPrintWriter(new PrintWriter(stringWriter));
            printStackTrace(wrappedPrintWriter, e);
            String s = stringWriter.toString();
            return (s == null || s.length() == 0) ? e.getMessage() : s;
        } catch (Exception e1) {
            return "StackTraceAsStringLimit error:" + e.getMessage();
        }
    }


    private static void printStackTrace(WrappedPrintWriter s, Throwable e) {
        // Guard against malicious overrides of Throwable.equals by
        // using a Set with identity equality semantics.
        Set<Throwable> dejaVu = Collections.newSetFromMap(new IdentityHashMap<Throwable, Boolean>());
        dejaVu.add(e);

        synchronized (s.lock()) {
            // Print our stack trace
            s.println(e);
            StackTraceElement[] trace = getOurStackTrace(e);
            int line = 0;
            for (StackTraceElement traceElement : trace) {
                line++;
                if (line > MAIN_MAX_LINE) {
                    break;
                }
                s.println("\tat " + traceElement);
            }
            if (line > MAIN_MAX_LINE) {
                s.println("\t... " + (trace.length - MAIN_MAX_LINE) + " main more");
            }

            // Print cause, if any
            Throwable ourCause = e.getCause();
            if (ourCause != null) {
                printEnclosedStackTrace(s, CAUSE_CAPTION, "", dejaVu, ourCause);
            }
        }
    }

    /**
     * Print our stack trace as an enclosed exception for the specified
     * stack trace.
     */
    private static void printEnclosedStackTrace(WrappedPrintWriter s,
                                                String caption,
                                                String prefix,
                                                Set<Throwable> dejaVu, Throwable ourCause) {
        assert Thread.holdsLock(s.lock());
        if (dejaVu.contains(ourCause)) {
            s.println("\t[CIRCULAR REFERENCE:" + ourCause + "]");
        } else {
            dejaVu.add(ourCause);

            // Print our stack trace
            s.println(prefix + caption + ourCause);
            /*
            // Compute number of frames in common between this and enclosing trace
            StackTraceElement[] trace = getOurStackTrace(ourCause);

            int m = trace.length - 1;
            int n = enclosingTrace.length - 1;
            while (m >= 0 && n >= 0 && trace[m].equals(enclosingTrace[n])) {
                m--;
                n--;
            }
            int framesInCommon = trace.length - 1 - m;

            int line = 0;
            for (int i = 0; i <= m; i++) {
                line++;
                if (line > MAX_LINE) {
                    framesInCommon += m - MAX_LINE;
                    break;
                }
                s.println(prefix + "\tat " + trace[i]);
            }
            if (framesInCommon != 0) {
                s.println(prefix + "\t... " + framesInCommon + " more");
            }*/

            // Print cause, if any
            Throwable ourCause2 = ourCause.getCause();
            if (ourCause2 != null) {
                printEnclosedStackTrace(s, CAUSE_CAPTION, prefix, dejaVu, ourCause2);
            }
        }
    }

    private static final StackTraceElement[] UNASSIGNED_STACK = new StackTraceElement[0];
    private static final String CAUSE_CAPTION = "Caused by: ";
    private static final int MAIN_MAX_LINE = 3;
    private static final int MAX_LINE = 1;
    private static final String LINE = "\n";

    private static synchronized StackTraceElement[] getOurStackTrace(Throwable e) {
        if (e == null) {
            return UNASSIGNED_STACK;
        } else {
            return e.getStackTrace();
        }
    }


    private static class WrappedPrintWriter {
        private final PrintWriter printWriter;

        WrappedPrintWriter(PrintWriter printWriter) {
            this.printWriter = printWriter;
        }

        Object lock() {
            return printWriter;
        }

        void println(Object o) {
            printWriter.print(o + LINE);
        }
    }

}
